unit itemselectorframeunit;

{$mode objfpc}{$H+}

interface

uses
  Classes, SysUtils, Forms, Controls, ExtCtrls, StdCtrls, Menus, fpjson,
  database, LCLTranslator;

type

  ItemDataReloadCallback = procedure(NewId: Integer) of object;

  { TItemSelectorFrame }

  TItemSelectorFrame = class(TFrame)
    ItemSelectorLabel: TLabel;
    ItemSelectorListBox: TListBox;
    ItemSelectorPanel: TPanel;
    ItemPopupMenu: TPopupMenu;
    CopyMenuItem: TMenuItem;
    ClearMenuItem: TMenuItem;
    PasteMenuItem: TMenuItem;
    SetMaximumButton: TButton;
    procedure ClearMenuItemClick(Sender: TObject);
    procedure CopyMenuItemClick(Sender: TObject);
    procedure FrameResize(Sender: TObject);
    procedure ItemPopupMenuPopup(Sender: TObject);
    procedure ItemSelectorListBoxMouseUp(Sender: TObject; Button: TMouseButton;
      Shift: TShiftState; X, Y: Integer);
    procedure ItemSelectorListBoxSelectionChange(Sender: TObject; User: boolean
      );
    procedure PasteMenuItemClick(Sender: TObject);
    procedure SetMaximumButtonClick(Sender: TObject);
  private
    DbArr: TJSONArray;
    FJsonDataType: String;
    FJsonDataTypeId: Integer;
    ResizerProcedure: DbResizerProcedure;
    FCreateElementCallback: CreateElementFn;
    ReloadProcedure: ItemDataReloadCallback;
    IsLoading: Boolean;
    function FindFittingLabelFontSize(TestTitle: String): Integer;
    procedure SetTitle(ATitle: String);
    procedure CopySelected;
    function IsSelectionContiguous: Boolean;
    procedure SetJsonDataType(Value: String);
  public
    procedure SetData(ADbArr: TJSONArray; AResizerProcedure: DbResizerProcedure;
      AReloadProcedure: ItemDataReloadCallback);
    procedure LoadItemList;
    procedure RefreshSelectedName;
    property Title: String write SetTitle;
    property JsonDataType: String read FJsonDataType write SetJsonDataType;
    property CreateElementCallback: CreateElementFn read FCreateElementCallback write FCreateElementCallback;
  end;

implementation

uses
  Dialogs, setmaximumformunit, clipboardhelper, Math, Graphics, Types;

procedure TItemSelectorFrame.SetData(ADbArr: TJSONArray;
  AResizerProcedure: DbResizerProcedure;
  AReloadProcedure: ItemDataReloadCallback);
begin
  IsLoading := True;
  DbArr := ADbArr;
  ResizerProcedure := AResizerProcedure;
  ReloadProcedure := AReloadProcedure;

  LoadItemList;
  ItemSelectorListBox.ItemIndex := 0;
  ItemSelectorListBox.Selected[0] := True;


  IsLoading := False;
end;

procedure TItemSelectorFrame.SetMaximumButtonClick(Sender: TObject);
var
  NewMaximum: Integer;
begin
  NewMaximum := SetMaximumForm.RequestValue(DbArr.Count - 1);
  ResizerProcedure(NewMaximum);
  if NewMaximum >= ItemSelectorListBox.ItemIndex then begin
    ItemSelectorListBox.ItemIndex := 0;
    ReloadProcedure(ItemSelectorListBox.ItemIndex + 1);
  end;
  LoadItemList;
end;

function TItemSelectorFrame.FindFittingLabelFontSize(TestTitle: String): Integer;
var
  TestBmp: Graphics.TBitmap;
  Size: TSize;
  FontSize: Integer;
  MaxWidth: Integer;
  DecreaseStep: Integer = 4;
begin
  TestBmp := Graphics.TBitmap.Create;
  FontSize := ItemSelectorLabel.Height;
  MaxWidth := ItemSelectorLabel.Width;
  with TestBmp.Canvas do begin
    Font.Assign(ItemSelectorLabel.Font);

    repeat
      Dec(FontSize, DecreaseStep);
      Font.Height := FontSize;
      Size := TextExtent(TestTitle);

      if FontSize < 12 then
        DecreaseStep := 2;
    until (Size.Width <= MaxWidth) or (FontSize <= 4);
  end;

  FreeAndNil(TestBmp);

  FindFittingLabelFontSize := FontSize;
end;

procedure TItemSelectorFrame.ItemSelectorListBoxSelectionChange(
  Sender: TObject; User: boolean);
begin
  { Make sure this is not the creation time, when the selection is changed to 0}
  if not IsLoading then
    if ItemSelectorListBox.Selected[ItemSelectorListBox.ItemIndex] then
      ReloadProcedure(ItemSelectorListBox.ItemIndex + 1);
end;

procedure TItemSelectorFrame.PasteMenuItemClick(Sender: TObject);
var
  PastedArray: TJSONArray;
  I: Integer;
  StartId: Integer;
begin
  StartId := ItemSelectorListBox.ItemIndex + 1;
  if StartId <= 0 then
    Exit;

  PastedArray := GetJsonArrayFromClipboard(JsonDataType);
  if PastedArray = nil then
    Exit;

  try
    for I := 0 to Min(PastedArray.Count -1,
                       DbArr.Count - 1 - ItemSelectorListBox.ItemIndex) do begin
      if PastedArray[I].JSONType = jtObject then begin
        DbArr.Objects[StartId + I] := PastedArray.Objects[I].Clone as TJSONObject;
        DbArr.Objects[StartId + I].Integers['id'] := StartId + I;
      end;
    end;

  finally
    PastedArray.Free;
  end;

  LoadItemList;
end;

procedure TItemSelectorFrame.ItemSelectorListBoxMouseUp(Sender: TObject;
  Button: TMouseButton; Shift: TShiftState; X, Y: Integer);
begin
  if Button = mbRight then begin
    ItemPopupMenu.PopUp;
  end;
end;

procedure TItemSelectorFrame.CopyMenuItemClick(Sender: TObject);
begin
  CopySelected
end;

procedure TItemSelectorFrame.FrameResize(Sender: TObject);
begin
  ItemSelectorLabel.Font.Height := FindFittingLabelFontSize(ItemSelectorLabel.Caption)
end;

procedure TItemSelectorFrame.ClearMenuItemClick(Sender: TObject);
var
  I: Integer;
  ReplacementItem: TJSONObject;
  SelectedNeedsReload: Boolean = False;
begin
  ItemSelectorListBox.Items.BeginUpdate;
  for I := 1 to DbArr.Count -1 do begin
    if ItemSelectorListBox.Selected[I - 1] then begin
      ReplacementItem := CreateElementCallback(I);
      DbArr.Objects[I] := ReplacementItem;
      if ItemSelectorListBox.ItemIndex = I - 1 then
        SelectedNeedsReload := True;

      ItemSelectorListBox.Items[I-1] := TDatabase.FormatObjName(ReplacementItem);
    end;
  end;
  ItemSelectorListBox.Items.EndUpdate;

  if SelectedNeedsReload then
    ReloadProcedure(ItemSelectorListBox.ItemIndex + 1);
end;

procedure TItemSelectorFrame.ItemPopupMenuPopup(Sender: TObject);
var
  ClipboardEnabled: Boolean;
begin
  ClipboardEnabled := FJsonDataType <> '';

  CopyMenuItem.Visible := ClipboardEnabled;
  PasteMenuItem.Visible := ClipboardEnabled;
  PasteMenuItem.Enabled := ClipboardHasData(FJsonDataType);
end;

procedure TItemSelectorFrame.LoadItemList;
var
  I: Integer;
  Item: TJSONObject;
begin
  ItemSelectorListBox.Items.BeginUpdate;
  ItemSelectorListBox.Items.Clear;
  for I := 1 to DbArr.Count - 1 do begin
    Item := DbArr.Objects[I] as TJSONObject;
    ItemSelectorListBox.Items.Append(TDatabase.FormatObjName(Item));
  end;
  ItemSelectorListBox.Items.EndUpdate;
end;

procedure TItemSelectorFrame.SetTitle(ATitle: String);
begin
  ItemSelectorLabel.Caption := ATitle;
  ItemSelectorLabel.Font.Height := FindFittingLabelFontSize(ATitle)
end;

procedure TItemSelectorFrame.CopySelected;
var
  ArrSerializer: TClipboardArraySerializer;
  I, NumCopied: Integer;
begin
  if not IsSelectionContiguous then begin
    if MessageDlg(
      'Non-contiguous selection', 'The selection is not contiguous (it contains gaps), so it will be flattened (the gaps will not be copied). Is that OK?',
      mtConfirmation,
      [mbYes, mbNo], 0) = mrNo then
        Exit;
  end;

  ArrSerializer := TClipboardArraySerializer.Create(FJsonDataTypeId);
  try
    NumCopied := 0;
    for I := 0 to ItemSelectorListBox.Count -1 do begin
      if ItemSelectorListBox.Selected[I] then begin
        ArrSerializer.AddItem(DbArr.Objects[I + 1]);
        Inc(NumCopied)
      end
    end;

    if (NumCopied = 0) and (ItemSelectorListBox.ItemIndex > -1) then begin
      ArrSerializer.AddItem(DbArr.Objects[ItemSelectorListBox.ItemIndex + 1]);
      Inc(NumCopied);
    end;

    if NumCopied > 0 then
      ArrSerializer.CopyToClipboard
  finally
    ArrSerializer.Free
  end;
end;

function TItemSelectorFrame.IsSelectionContiguous: Boolean;
var
  StartSel: Integer = -1;
  EndSel: Integer = -1;
  I: Integer;
begin
  I := 0;
  while (StartSel = -1) and (I < ItemSelectorListBox.Count) do begin
    if ItemSelectorListBox.Selected[I] then
      StartSel := I;
    Inc(I);
  end;

  EndSel := StartSel;
  while (EndSel < ItemSelectorListBox.Count -1) and ItemSelectorListBox.Selected[EndSel + 1] do
    Inc(EndSel);

  if EndSel = ItemSelectorListBox.Count -1 then begin
    IsSelectionContiguous := True;
    Exit;
  end;

  I := EndSel + 1;
  while (I < ItemSelectorListBox.Count) and not ItemSelectorListBox.Selected[I] do
    Inc(I);

  if (I <= ItemSelectorListBox.Count -1) and (ItemSelectorListBox.Selected[I]) then
    IsSelectionContiguous := False
  else
    IsSelectionContiguous := True;
end;

procedure TItemSelectorFrame.SetJsonDataType(Value: String);
begin
  FJsonDataType := Value;
  FJsonDataTypeId := GetDataTypeId(Value);
end;

procedure TItemSelectorFrame.RefreshSelectedName;
var
  ItemId: Integer;
begin
  ItemId := ItemSelectorListBox.ItemIndex + 1;
  ItemSelectorListBox.Items[ItemId - 1] := TDatabase.FormatObjName(DbArr.Objects[ItemId]);
end;

{$R *.lfm}

end.

